import { Graph, Addon, Shape, DataUri } from "@antv/x6";
import CustomEdge from './CustomEdge'
import DragNode from "@/DragNode";
import Utils from '@/Utils'
import eventBus from './EventBus'
import FileSaver from 'file-saver'
export default class FlowGraph {
    graph
    dnd
    vm
    ArtifactClass
    constructor(vm, grid, toolbar) {
        this.vm = vm
        this.register()
        this.graph = new Graph({
            container: document.getElementById(grid),
            grid: true,
            clipboard: true,
            keyboard: {
                enabled: true,
                global: true
            },
            history: true,
            resizing: {
                enabled: (node) => { return node.shape === 'html' || node.shape === 'circle' }
            },
            selecting: {
                enabled: true,
                showNodeSelectionBox: true,
                rubberband: true
            },
            minimap: {
                enabled: true,
                container: document.getElementById('map'),
                width: 200,
                height: 160,
                padding: 10,
            },
            panning: {
                enabled: true,
                eventTypes: ['rightMouseDown', 'mouseWheel'],
                modifiers: 'ctrl',
            },
            mousewheel: {
                enabled: true,
                zoomAtMousePosition: true,
                modifiers: 'ctrl',
                minScale: 0.5,
                maxScale: 3,
            },
            embedding: {
                enabled: true,
                validate({ child, parent }) {
                    if (Utils.getCellType(child) === 'producer-node' && Utils.getCellType(parent)==='service-node') {
                        child.data.model.service = parent.id
                        return true
                    } else return false
                }
            },
            connecting: {
                router: 'manhattan',
                connector: {
                    name: 'rounded',
                    args: {
                        radius: 8,
                    },
                },
                anchor: 'center',
                connectionPoint: 'anchor',
                allowBlank: false,
                snap: {
                    radius: 20,
                },
                // 这里是连边的钩子函数，传入的参数只能获取到起点
                createEdge({ sourceCell }) {
                    // 如果起点是库元素（圆）那么创建引用读的线，后面引用读的线可以更改样式为只读（本质上是拷贝读，这样不影响原数据）
                    // 否则起点就是服务元素了，那么创建写入线
                    if (sourceCell.shape === 'html' && sourceCell.html === 'circle') {
                        let edge = new Shape.Edge({
                            attrs: CustomEdge.createReferenceReadLine(),
                            zIndex: 0,
                            data: { type: 'referenceRead-edge' },
                            connector: {
                                name: 'jumpover',
                            }
                        })
                        return edge
                    } else {
                        let edge = new Shape.Edge({
                            attrs: CustomEdge.createWriteLine(),
                            zIndex: 0,
                            data: { type: 'write-edge' },
                            connector: {
                                name: 'jumpover',
                            }
                        })
                        return edge
                    }
                },
            }
        })
        this.initDataEventBus()
        this.initGraphEvent()
        this.initEventBus()
        this.initKeyboard()
        this.dnd = new Addon.Dnd({
            target: this.graph,
            containerParent: document.getElementById(toolbar),
        })
        this.ArtifactClass = ['jjj']
        console.log(this.ArtifactClass.toString())
        // this.init()
    }
    // 初始化图，里面可以实验一些例子
    init() {
        let node2 = this.graph.addNode({
            shape: 'message-node',
            x: 400,
            y: 300,
            label: 'message-node'
        })
        node2.label = 'node'
        node2.data = { label: 'node', type: 'message-node' }

        let node3 = this.graph.addNode({
            shape: 'artifact-node',
            x: 500,
            y: 300,
            label: 'artifact-node'
        })
        node3.label = 'node'
        node3.data = { label: 'node', type: 'artifact-node' }

        let node4 = this.graph.addNode({
            shape: 'after-node',
            x: 600,
            y: 300,
            label: 'after-node'
        })
        node4.label = 'node'
        node4.data = { label: 'node', type: 'after-node' }

        let node5 = this.graph.addNode({
            shape: 'before-node',
            x: 630,
            y: 300,
            label: 'E5'
        })
        node5.label = 'node'
        node5.data = { label: 'node', type: 'before-node' }

        let node1 = this.graph.addNode({
            shape: 'circle',
            width: 100,
            height: 100,
            x: 750,
            y: 500,
        })
        node1.label = 'Menus'
        node1.data = { label: 'Menus', type: 'repository' }
        let node = this.graph.addNode({
            shape: 'html',
            width: 140,
            height: 200,
            x: 700,
            y: 300,
            html: 'service',
            ports: DragNode.getServicePort()
        })
        Utils.findTopRect(node.id).innerHTML = 'ADD ITEMS AND CREATE KITCHEN ORDERS'
        node.data = { label: 'ADD ITEMS AND CREATE KITCHEN ORDERS', type: 'service' }

        let edge = new Shape.Edge({
            attrs: CustomEdge.createReadLine(),
            zIndex: 0,
            source: { x: 100, y: 100 },
            target: { x: 400, y: 100 },
            data: { type: 'read-edge' }
        })
        this.graph.addEdge(edge)
        // edge1.attrs = CustomEdge.createConditionReadLine()
        // console.log(edge1)
        let edge2 = new Shape.Edge({
            attrs: CustomEdge.createWriteLine(),
            zIndex: 0,
            source: { x: 100, y: 200 },
            target: { x: 400, y: 200 },
            data: { type: 'write-edge' }
        })
        this.graph.addEdge(edge2)
        let edge3 = new Shape.Edge({
            attrs: CustomEdge.createConditionReadLine(),
            zIndex: 0,
            source: { x: 100, y: 150 },
            target: { x: 400, y: 150 },
            data: { type: 'condition-edge' },
            tools: ['segments']
        })
        this.graph.addEdge(edge3)
    }
    initDataEventBus() {
        eventBus.$on('ArtifactClass:add', artifact => {
            if(!artifact.label) return 
            if(this.ArtifactClass.some(item => item.label === artifact.label)) {
                this.vm.$notify({
                    title: "错误",
                    message: "命名重复！",
                    type: "error",
                    offset: 50,
                  });
                return
            }
            this.ArtifactClass.push(JSON.parse(JSON.stringify(artifact)))
            this.vm.$notify({
                title: "成功",
                message: "新增Artifact类成功！",
                type: "success",
                offset: 50,
              });
        })
        // eventBus.$on('ArtifactClass:delete', label => {
        //     for(let i = 0; i < this.ArtifactClass.length; i++) {
        //         if(this.ArtifactClass[i].label === label) {
        //             this.ArtifactClass.splice(i,1)
        //             this.vm.$notify({
        //                 title: "成功",
        //                 message: "删除Artifact类成功！",
        //                 type: "success",
        //                 offset: 50,
        //               });
        //             return
        //         }
        //     }
        // })
        eventBus.$on('ArtifactClass:get', (label,callback) => {
            let artifact = this.ArtifactClass.find(item => item.label===label)
            console.log(artifact)
            callback(JSON.parse(JSON.stringify(artifact)))
        })
        eventBus.$on('ArtifactClass:getAllLabel', (callback) => {
            let labels = this.ArtifactClass.map(item => item.label)
            console.log(labels)
            callback(labels)
        })
        eventBus.$on('ArtifactClass:getStates', (label, callback)=> {
            for(let i=0;i<this.ArtifactClass.length;i++) {
                if(this.ArtifactClass[i].label === label) {
                    console.log([...this.ArtifactClass[i].stateList])
                    callback([...this.ArtifactClass[i].stateList])
                    break
                }
            }
        })
        // eventBus.$on('ArtifactClass:update', (label, artifact) => {
        //     for(let i=0;i<this.ArtifactClass.length;i++) {
        //         if(this.ArtifactClass[i].label === label) {
        //             this.ArtifactClass[i] = JSON.parse(JSON.stringify(artifact))
        //             this.vm.$notify({title: "成功", message: "修改Artifact类成功！", type: 'success', offset: 50})
        //             return
        //         }
        //     }
        // })
        eventBus.$on('Event:get', (label, callback) => {
            let nodes = this.graph.getNodes()
            let res = {producer:'', consumer:'', message:''}
            nodes.forEach(item => {
                if(item.data.label === label) {
                    if(Utils.getCellType(item)==='producer-node') {
                        let snode = this.graph.getCellById(item.data.model.service)
                        if(snode) res.producer = snode.data.label
                    }
                    else if(Utils.getCellType(item)==='consumer-node') {
                        let snode = this.graph.getCellById(item.data.model.service)
                        let mnode = this.graph.getCellById(item.data.model.message)
                        if(snode) res.consumer = snode.data.label
                        if(mnode) res.message = mnode.data.label
                    }
                }
            })
            callback(res)
        })
        eventBus.$on('Event:getMessages', callback => {
            let nodes = this.graph.getNodes()
            let res = []
            nodes.forEach(item => {
                if(Utils.getCellType(item)==='message-node') res.push({id:item.id, label:item.data.label})
            })
            callback(res)
        })
        eventBus.$on('Tree:update', () => {
            eventBus.$emit('graph:updated', this)
        })
    }
    initEventBus() {
        eventBus.$on('updateSize', msg => {
            let cell = this.graph.getCellById(msg.id)
            if (cell.shape === 'html' && cell.html === 'circle') {
                Utils.findCircle(cell.id).style.fontSize = msg.size + 'px'
            } else if (cell.shape === 'html') Utils.findTopRect(cell.id).style.fontSize = msg.size + 'px'
        })
        eventBus.$on('updateStyle', msg => {
            let cell = this.graph.getCellById(msg.id)
            console.log(msg)
            if (msg.style === '读取') {
                cell.attrs = { line: { strokeDasharray: 0 } }
                cell.setData({ type: 'referenceRead-edge' })
            }
            else if (msg.style === '只读') {
                cell.attrs = { line: { strokeDasharray: 5 } }
                cell.setData({ type: 'cloneRead-edge' })
            } else if (msg.style === '循环服务') {
                let csize = Utils.findTopRect(cell.id).style.fontSize
                cell.html = 'service2'
                let ele = Utils.findTopRect(cell.id)
                ele.innerHTML = cell.data.label
                ele.style.fontSize = csize
            } else if (msg.style === '一般服务') {
                let csize = Utils.findTopRect(cell.id).style.fontSize
                cell.html = 'service'
                let ele = Utils.findTopRect(cell.id)
                ele.innerHTML = cell.data.label
                ele.style.fontSize = csize
            }
        })
        eventBus.$on('addPort', msg => {
            let cell = this.graph.getCellById(msg.id)
            if (msg.direction === 0) cell.addPort({ group: 'left' })
            else if (msg.direction === 1) cell.addPort({ group: 'right' })
            else if (msg.direction === 2) cell.addPort({ group: 'top' })
        })
        eventBus.$on('removePort', msg => {
            let cell = this.graph.getCellById(msg.id)
            this.flowGraph.removeEmptyPort(cell)
        })
        eventBus.$on('updateLabel', msg => {
            let cell = this.graph.getCellById(msg.id)
            cell.data.label = msg.label
            if (Utils.getCellType(cell)==='circle-node') {
                Utils.findCircle(cell.id).innerHTML = msg.label
            } else if (Utils.getCellType(cell).indexOf('service')!==-1) {
                Utils.findTopRect(cell.id).innerHTML = msg.label
            } else {
                cell.label = msg.label
            }
            eventBus.$emit('graph:updated', this)
        })
        eventBus.$on('fileOut', () => {
            let jsonData = this.graph.toJSON()
            jsonData.model = JSON.parse(JSON.stringify(this.ArtifactClass))
            const data = JSON.stringify(jsonData)
            const blob = new Blob([data], { type: '' })
            FileSaver.saveAs(blob, 'artiflow.json')
        })
        eventBus.$on('fileOutPng', () => {
            this.graph.toPNG(uri => {
                DataUri.downloadDataUri(uri, 'flowchart.png')
            })
        })
        eventBus.$on('fileIn', data => {
            this.graph.fromJSON(data)
            this.ArtifactClass = data.model || []
            let nodes = this.graph.getNodes()
            nodes.forEach(item => {
                if (Utils.getCellType(item)==='circle-node') {
                    Utils.findCircle(item.id).innerHTML = item.data.label
                } else if (Utils.getCellType(item).indexOf('service')!==-1) {
                    Utils.findTopRect(item.id).innerHTML = item.data.label
                }
            })
        })
        eventBus.$on('saveToLocal' , ()=>{
            let jsonData = this.graph.toJSON()
            jsonData.model = JSON.parse(JSON.stringify(this.ArtifactClass))
            console.log(jsonData)
            localStorage.setItem('localData', JSON.stringify(jsonData))
          })
      
          eventBus.$on('clearAllData',()=>{
            const cells = this.graph.getCells()
            if (cells.length) {
                this.graph.removeCells(cells)
            }
            this.ArtifactClass = []
          })
          
          eventBus.$on('selectAll',()=>{
            this.graph.select(this.graph.getNodes())
          })
      
          eventBus.$on('zoomListen',()=>{
            setTimeout(()=>{
              eventBus.$emit('zoomGet',this.graph.zoom())
            },0)
          })
          eventBus.$on('zoomIn',()=>{
            if (3 - this.graph.zoom() < 0.1) {
              this.graph.zoomTo(3)
            } else {
              this.graph.zoom(0.1)
            }
            setTimeout(()=>{
              eventBus.$emit('zoomGet',this.graph.zoom())
            },0)
          })
      
          eventBus.$on('zoomOut',()=>{
            if (this.graph.zoom() - 0.5 < 0.1) {
              this.graph.zoomTo(0.5)
            } else {
              this.graph.zoom(-0.1)
            }
            setTimeout(()=>{
              eventBus.$emit('zoomGet',this.graph.zoom())
            },0)
          })
      
          eventBus.$on('undo',()=>{
            if (this.graph.history.canUndo()) {
                this.graph.history.undo()
            }
          })
      
          eventBus.$on('redo',()=>{
            if (this.graph.history.canRedo()) {
                this.graph.history.redo()
            }
          })
    }
    // 图添加连线事件，这里可以做更多的关于连线的钩子函数，因为此时线已连好，可以获取到终点了
    // 图添加鼠标悬浮边上事件，这里可以增加线的工具，也就是添加线的拐点和平移线段
    // 图添加鼠标离开边事件，移除线的工具
    // 图添加鼠标悬浮节点事件，这里可以显示出所有节点的连接点
    // 图添加鼠标离开节点事件，这里隐藏所有连接点
    // 图添加节点选中、新增、删除事件
    initGraphEvent() {
        const container = document.getElementById('grid')
        this.graph.on('edge:connected', ({ edge }) => {
            let source = this.graph.getCellById(edge.source.cell)
            let target = this.graph.getCellById(edge.target.cell)
            if (Utils.getCellType(source) === 'consumer-node') {
                if (Utils.getCellType(target) === 'service-node') {
                    source.data.model.service = target.id
                } else {
                    this.vm.$message.error('触发事件节点必须与一般服务节点相连')
                    this.graph.removeCell(edge.id)
                }
            }
        })
        this.graph.on('edge:mouseenter', ({ edge }) => {
            // console.log(edge)
            edge.addTools([
                {
                    name: 'vertices',
                    args: {
                        stopPropagation: false,
                    }
                },
                {
                    name: 'segments',
                    args: {
                        stopPropagation: false,
                    }
                }
            ])
        })
        this.graph.on('edge:mouseleave', ({ edge }) => {
            edge.removeTools()
        })
        this.graph.on('node:mouseenter', () => {
            const ports = container.querySelectorAll(
                '.x6-port-body',
            )
            this.showPorts(ports, true)
        })
        this.graph.on('node:mouseleave', () => {
            const ports = container.querySelectorAll(
                '.x6-port-body',
            )
            this.showPorts(ports, false)
            // node.removeTools()
        })
        this.graph.on('cell:selected', ({ cell }) => {
            console.log(cell)
            eventBus.$emit('select', cell)
        })
        this.graph.on('cell:added', () => {
            eventBus.$emit('graph:updated', this)
        })
        this.graph.on('cell:removed', () => {
            eventBus.$emit('graph:updated', this)
        })
    }
    showPorts(ports, show) {
        for (let i = 0, len = ports.length; i < len; i = i + 1) {
            ports[i].style.visibility = show ? 'visible' : 'hidden'
        }
    }

    register() {
        Graph.registerHTMLComponent('service', DragNode.createServiceNode)
        Graph.registerHTMLComponent('service2', DragNode.createSelfServiceNode)
        Graph.registerHTMLComponent('circle', DragNode.createCircleNode)
        Graph.registerNode(
            'producer-node',
            DragNode.createProducerNode()
        )
        Graph.registerNode(
            'consumer-node',
            DragNode.createConsumerNode()
        )
        Graph.registerNode(
            'artifact-node',
            DragNode.createArtifactNode()
        )
        Graph.registerNode(
            'message-node',
            DragNode.createMessageNode()
        )
    }

    initKeyboard() {
        //undo redo
        this.graph.bindKey(['meta+z', 'ctrl+z'], () => {
            if (this.graph.history.canUndo()) {
                this.graph.history.undo()
            }
            return false
        })
        this.graph.bindKey(['meta+shift+z', 'ctrl+shift+z'], () => {
            if (this.graph.history.canRedo()) {
                this.graph.history.redo()
            }
            return false
        })

        //delete
        this.graph.bindKey('delete', () => {
            const cells = this.graph.getSelectedCells()
            if (cells.length) {
                this.graph.removeCells(cells)
            }
        })

        //select all
        this.graph.bindKey(['meta+a', 'ctrl+a'], () => {
            this.graph.select(this.graph.getNodes())
        })

        //copy and paste
        this.graph.bindKey(['meta+c', 'ctrl+c'], () => {
            const cells = this.graph.getSelectedCells()
            if (cells.length) {
                this.graph.copy(cells)
            }
            return false
        })
        this.graph.bindKey(['meta+v', 'ctrl+v'], () => {
            if (!this.graph.isClipboardEmpty()) {
                const cells = this.graph.paste({ offset: 32 })
                this.graph.cleanSelection()
                this.graph.select(cells)
            }
            return false
        })
    }

    removeEmptyPort(cell) {
        let edges = this.graph.getEdges()
        let ports = cell.getPorts()
        ports.forEach(item => {
            let flag = false
            for (let i in edges) {
                if (edges[i].target.port === item.id || edges[i].source.port === item.id) {
                    flag = true
                    break
                }
            }
            if (!flag) cell.removePort(item.id)
        })
    }
}
